﻿//
// Author: Eldar Radovici
// Email: support@MvcDataTables.NET
// Website: http://MvcDataTables.NET
// Source: http://MvcDataTables.CodePlex.com/
// License: http://MvcDataTables.CodePlex.com/license
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;
using System.Web.Mvc;

namespace MvcDataTables
{
    public class DataTableResult<T> : JsonResult
    {
        #region Private fields
        private readonly Controller _controller;
        #endregion

        public DataTableResult(Controller controller, IEnumerable<T> items, DataTableViewModel dataTableViewModel)
            : this(controller, items, dataTableViewModel, DefaultFilterFunction, DefaultTransformFunction)
        {
        }

        public DataTableResult(Controller controller, IEnumerable<T> items, DataTableViewModel dataTableViewModel, Func<T, string, bool> filterFunction, Func<T, string[]> transformFunction)
        {
            _controller = controller;
            var filteredItems = (from item in items
                                 where filterFunction(item, dataTableViewModel.sSearch ?? string.Empty)
                                 select item);
            var sortedItems = Sort(filteredItems.AsQueryable()); //NOTE: IQueryable because OrderBy must take an expression of a delegate
            var pagedItems = (from item in sortedItems
                                 .Skip(dataTableViewModel.iDisplayStart)
                                 .Take(dataTableViewModel.iDisplayLength)
                              select item).ToList();
            var transformedItems = pagedItems.Select(transformFunction).ToList();

            IEnumerable<object> columns = null;
            if (typeof(IDictionary<string, object>).IsAssignableFrom(typeof(T)))
            {
                IDictionary<string, object> item = pagedItems.FirstOrDefault() as IDictionary<string, object>;
                if (item != null)
                {
                    columns = item.Keys.Select(lmb => new {sTitle = lmb});
                }                
            }            

            this.Data = new
            {
                sEcho = dataTableViewModel.sEcho,
                iTotalRecords = items.Count(),
                iTotalDisplayRecords = filteredItems.Count(),
                aaData = transformedItems,
                aoColumns = columns //new []{ new { sTitle = "One"}, new {sTitle = "Two"}, new {sTitle = "Three"}, new {sTitle = "Four"} }
            };
            this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
        }

        private static string[] Properties
        {
            get
            {
                return typeof(T).GetProperties().Select(lmb => lmb.Name).ToArray();
            }
        }

        public static Func<T, string, bool> DefaultFilterFunction
        {
            get
            {
                if (typeof(IDictionary<string,object>).IsAssignableFrom(typeof(T)))
                {
                    return (item, filter) => ((IDictionary<string, object>)item).Values.FirstOrDefault(lmb => lmb.ToStringOrDefault().ToLower().Contains(filter.ToLower())) != null;
                }
                else
                {
                    PropertyInfo[] propertyInfos = typeof(T).GetProperties();
                    return (item, filter) => propertyInfos.FirstOrDefault(lmb => lmb.GetValue(item, null).ToStringOrDefault().ToLower().Contains(filter.ToLower())) != null;                    
                }
            }
        }

        public static Func<T, string[]> DefaultTransformFunction
        {
            get
            {
                if (typeof(IDictionary<string, object>).IsAssignableFrom(typeof(T)))
                {
                    return item => ((IDictionary<string, object>)item).Values.Select(lmb => lmb.ToStringOrDefault()).ToArray();
                }
                else
                {
                    PropertyInfo[] propertyInfos = typeof (T).GetProperties();
                    return item => propertyInfos.Select(lmb => lmb.GetValue(item, null).ToStringOrDefault()).ToArray();
                }
            }
        }

        public static string[] GetDefaultOrderedTransformFunction(T item, string[] orderedProperties)
        {
            List<string> orderedValues = new List<string>();
            foreach(string property in orderedProperties)
            {
                string value;
                if (typeof(IDictionary<string, object>).IsAssignableFrom(typeof(T)))
                {
                    value = ((IDictionary<string, object>) item)[property].ToStringOrDefault();
                }
                else
                {
                    PropertyInfo propertyInfo = typeof(T).GetProperties().Single(lmb => lmb.Name.Equals(property, StringComparison.CurrentCultureIgnoreCase));
                    value = propertyInfo.GetValue(item, null).ToStringOrDefault();
                }
                orderedValues.Add(value);
            }
            return orderedValues.ToArray();
        }

        //http://weblogs.asp.net/zowens/archive/2010/01/19/jquery-datatables-plugin-meets-c.aspx
        private const string INDIVIDUAL_SORT_KEY_PREFIX = "iSortCol_";
        private const string INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX = "sSortDir_";
        private const string ASCENDING_SORT = "asc";
        private IQueryable<T> Sort(IQueryable<T> items)
        {
            if (typeof(IDictionary<string, object>).IsAssignableFrom(typeof(T)))
            {
                return items;
            }
            string[] properties = Properties;
            HttpRequestBase request = _controller.Request;
            foreach (string key in _controller.Request.Params.AllKeys.Where(lmb => lmb.StartsWith(INDIVIDUAL_SORT_KEY_PREFIX)))
            {
                int sortcolumn = int.Parse(request[key]);
                string sortdir = request[INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX + key.Replace(INDIVIDUAL_SORT_KEY_PREFIX, string.Empty)];
                var paramExpr = Expression.Parameter(typeof(T));
                var expression = Expression.Convert(Expression.Property(paramExpr, properties[sortcolumn]), typeof(object));
                if (!typeof(IComparable).IsAssignableFrom(expression.Operand.Type)) continue;
                var propertyExpr = Expression.Lambda<Func<T, object>>(expression, paramExpr);
                if (string.IsNullOrEmpty(sortdir) || sortdir.Equals(ASCENDING_SORT, StringComparison.OrdinalIgnoreCase))
                    items = items.OrderBy(propertyExpr);
                else
                    items = items.OrderByDescending(propertyExpr);
            }
            return items;
        }
    }
}
